package com.jf.anmocker.plugin.mockcore

import com.jf.mocker.anotations.MethodMockType
import javassist.CtClass
import javassist.CtField
import javassist.CtMethod
import javassist.Modifier

class ReplaceAgentMocker : IMethodMockAble {

    companion object{
        const val AGENT_INDEX = "AGENT_INDEX"
    }

    override fun modifyMethod(targetCls: CtClass, method: CtMethod, ano: MethodAnoEntity): Boolean {
        var modifiedFlag = false
        println("MethodMocker >>> modifyMethod ${ano.methodMocker.name} : ${ano.methodMocker.mockType}")
        when(ano.methodMocker.mockType){
            MethodMockType.Before -> {
                modifiedFlag = insertBefore(targetCls, method, ano)
            }
            MethodMockType.After -> {
                modifiedFlag = insertAfter(targetCls, method, ano)
            }
            MethodMockType.Replace -> {
                modifiedFlag = replace(targetCls, method, ano)
            }
            else -> println("unknown method mock-type : ${ano.methodMocker.mockType}")
        }
        //执行mock
        return modifiedFlag
    }

    override fun insertBefore(targetCls : CtClass, method : CtMethod, ano : MethodAnoEntity) : Boolean{
        try {
            val originMethod = quireOriginMethod(targetCls, method)
            method.setBody(ano.method, null)
            //ano的内容应该在前面，所以此处应该是将originMethod放在后面
            method.insertAfter("{${originMethod.name}($$);}")
            println("method[${method.name}] insertBefore perform")
            return true
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return false
    }

    override fun insertAfter(targetCls : CtClass, method : CtMethod, ano : MethodAnoEntity) : Boolean{
        try {
            val originMethod = quireOriginMethod(targetCls, method)
            method.setBody(ano.method, null)
            //ano的内容应该在后面，所以此处应该是将originMethod放在前面
            method.insertBefore("{${originMethod.name}($$);}")
            println("method[${method.name}] insertAfter perform")
            return true
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return false
    }

    override fun replace(targetCls : CtClass, method : CtMethod, ano : MethodAnoEntity) : Boolean{
        try {
            method.setBody(ano.method, null)
            println("method[${method.name}] setBody perform")
            return true
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return false
    }

    private fun quireOriginMethod(targetCls : CtClass, method : CtMethod, increase : Boolean = true) : CtMethod{
        var agentName = method.name+"\$origin"
        var agentMethod = MethodMockerManager.getDeclaredMethod(targetCls, agentName, method.parameterTypes)
        if(agentMethod == null){
            agentMethod = addAgentMethod(targetCls, method, agentName)
        }else if(increase){
            val indexField = quireAgentIndexFiled(targetCls)
            val indexValue = indexField.constantValue as Int
            agentName = method.name+"\$origin${indexValue + 1}"
            agentMethod = addAgentMethod(targetCls, method, agentName)
            targetCls.removeField(indexField)//移除重新添加记录代理函数index变量才能更新
            quireAgentIndexFiled(targetCls, indexValue + 1)
        }
        println("quireOriginMethod[$agentName] >>> increase = $increase")
        return agentMethod
    }

    private fun addAgentMethod(targetCls : CtClass, originMethod : CtMethod, agentName : String) : CtMethod{
        val agentMethod = CtMethod(originMethod.returnType, agentName, originMethod.parameterTypes, targetCls)
        agentMethod.modifiers = Modifier.PRIVATE
        agentMethod.setBody(originMethod, null)
        targetCls.addMethod(agentMethod)
        return agentMethod
    }

    private fun quireAgentIndexFiled(targetCls : CtClass, initValue : Int = 0) : CtField{
        var filed = MethodMockerManager.getDeclaredFiled(targetCls, AGENT_INDEX)
        if(filed == null){
            filed = CtField(CtClass.intType, AGENT_INDEX, targetCls)
            filed.modifiers = Modifier.PRIVATE + Modifier.STATIC + Modifier.FINAL
            targetCls.addField(filed, CtField.Initializer.constant(initValue))
        }
        return filed
    }

}